/**
 * 自定义Promise函数模块: IIFE ???
 */


(function (params) {

  // 定义状态常量, 注意常量都是大写的 | 和定位的位置
  const PENDING = 'pending'
  const RESOLVED = 'resolved'
  const REJECTED = 'rejected'

  /**
   * Promise构造函数
   *
   * @constructor
   * @param executor 执行器函数 | 同步执行函数
   */
  function Promise(executor) {
    /**
     * ES5写法, 把this保存到一个变量中, 然后将将所有使用this的地方换成 _this
     */
    const _this = this
    // 给promise对象指定status属性, 初始值为pending
    this.status = PENDING
    // 给promise对象指定一个用于存储数据的属性
    this.data = undefined
    // 每个元素的结构: { onResolved() {}, onRejected() {} }
    this.callbacks = []


    // 构造函数内部定义的resolve和reject函数
    /**
     * ES6写法
     * Tips: 使用箭头函数 const resolve = (value) => {} 代替 function resolve(value){} 定义函数
     * 箭头函数方式定义函数, 里面的this是指定Promise构造函数, 而第二种方式内this指定window
     */
    const resolve = (value) => {
      // 如果当前状态不是pending, 直接return
      if (this.status !== PENDING) return

      // 将状态改为resolved
      this.status = RESOLVED
      // 保存value的数据
      this.data = value
      // 如果有待执行的callback函数, 立即异步执行回调函数onResolved
      if (this.callbacks.length > 0) {
        setTimeout(() => {    // 放入队列中执行所有成功的回调
          this.callbacks.forEach(callbacksObj => {
            callbacksObj.onResolved(value)
          })
        })
      }
    }

    const reject = (reason) => {
      // 如果当前状态不是pending, 直接return
      if (this.status !== PENDING) return

      // 将状态改为rejected
      this.status = REJECTED
      // 保存value的数据
      this.data = reason
      // 如果有待执行的callback函数, 立即异步执行回调函数onRejected
      if (this.callbacks.length > 0) {
        setTimeout(() => {    // 放入队列中执行所有失败的回调
          this.callbacks.forEach(callbacksObj => {
            callbacksObj.onRejected(reason)
          })
        })
      }
    }

    // 立即同步执行executor
    try {
      executor(resolve, reject)
    } catch (e) {
      // 如果执行器抛出, 直接调用reject方法, 将promise对象变为rejected状态
      reject(e)
    }
  }

  /**
   * Promise原型对象的then() | then方法, new Promise()得到的实例对象可以访问
   *
   * @param onResolved  指定成功的回调函数
   * @param onRejected  指定失败的回调函数
   * @return 返回一个新的promise对象
   */
  Promise.prototype.then = function (onResolved, onRejected) {
    // 向后传递成功的value  --> 效果实现catch语句, 可以向后传递成功value
    onResolved = typeof onResolved === 'function' ? onResolved : value => value

    // 向后传递失败的reason  | [必须是函数, 不能是undefined]
    // 指定默认的失败的回调 --- (实现异常穿透关键点)
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw reason
    }

    // 返回一个新的promise对象
    return new Promise((resolve, reject) => {
      /**
       * 调用指定回调函数处理, 根据执行结果, 改变return的promise的状态
       *
       * @param callback 执行的回调函数 onResolved 或 onRejected
       */
      const handler = (callback) => {
        /**
         * 1.如果执行抛出异常: return的Promise对象状态变为rejected
         * 2.如果回调函数返回的非promise, 新的promise变为resolved
         * 3.如果回调函数返回是另一个新promise, return的promise结果就是这个promise的结果
         */
        try {
          const result = callback(this.data)
          // 使用instanceof, 判断对象是不是这个数据类型 | 语法: 对象 instanceof 构造函数名字
          // 3.如果回调函数返回是另一个新promise, return的promise结果就是这个promise的结果
          if (result instanceof Promise) {
            // result.then(
            //   value => resolve(value),  // 当result成功时, 让return的promise也成功
            //   reason => reject(reason)) // 当result失败时, 让return的promise也失败

            // 上面的简洁写法
            // JS    arr.forEach(console.log)  注意下forEach算子形参3个 value, index, arr
            // Java  arr.forEach(System.out::println)   // 函数引用 (lambda进一步优化)
            result.then(resolve, reject)
          } else {
            // 2.如果回调函数返回的非promise, 新的promise变为resolved
            resolve(result)
          }
        } catch (error) {
          // 1.如果执行抛出异常: return的Promise对象状态变为rejected
          reject(error)
        }
      }


      if (this.status === PENDING) {
        // 假设当前状态还是pending状态, 将回调函数保存起来
        // this.callbacks.push({onResolved, onRejected})

        // Tips: 不仅要保存两个函数, 还要改变then方法返回的新promise的状态
        // 这段代码没有理解
        this.callbacks.push({
          // 放入一个 func(), 而不是func的话, 会执行这个函数, 将函数的返回值传递给push()中
          // 而handler函数的返回值, 是一个新的resolved或rejected状态的promise对象

          // 自定义两个函数
          onResolved(value) {
            handler(onResolved)
          },
          onRejected(reason) {
            handler(onRejected)
          }
        })
      } else if (this.status === RESOLVED) {
        // 如果不是pending状态(状态先改变), 则去异步执行onResolved/onRejected并改变return新promise状态
        setTimeout(() => {
          handler(onResolved)
        })
      } else {    // rejected状态
        setTimeout(() => {
          handler(onRejected)
        })
      }
    })
  }

  /**
   * Promise原型对象的catch()
   *
   * @param onRejected  指定失败的回调函数
   * @return 返回一个新的promise对象
   */
  Promise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected)
  }

  /**
   * Promise函数对象的resolve() | resolve方法, Promise函数对象可以访问
   *
   * @param value 成功的value
   * @return 返回一个指定结果为value,且是成功的promise
   */
  Promise.resolve = function (value) {

  }

  /**
   * Promise函数对象的reject()
   *
   * @param reason 失败的reason
   * @return 返回一个指定结果为reason,且是失败的promise
   */
  Promise.reject = function (reason) {

  }

  /**
   * Promise函数对象的all()
   *
   * @param promises  promise实例对象数组, Tips: 里面可以不是promise实例对象
   * @return 返回一个promise,只有当所有promise都成功才会成功,否则只要有一个失败就失败
   */
  Promise.all = function (promises) {
    // 用来保存所有成功value的数据, 怎么样创建数组时指定长度, 使用Array构造函数
    const values = new Array(promises.length)
    // 用于保存成功promise的数量
    let resolveCount = 0
    return new Promise((resolve, reject) => {
        // 遍历promises获取每个promise的结果
        promises.forEach((p, index) => {
          // 确保p是Promise数据类型 | (1)判断类型 (2) Promise.resolve()包一层(省去了判断)
          Promise.resolve(p).then(value => {
            resolveCount++
            // p成功, 将成功的value保存values
            values[index] = value
            // 不能这样写, 因为这样的话, 先成功的回调函数先放, 后成功后方放 --> [空属性 × 3, 2, 3, 5] 现象
            // values.push(value)

            // 如果全部成功了, 将return的promise改变成功
            if (resolveCount === promises.length) {
              resolve(values)
            }
          }, reason => {
            // 只要有一个失败了, return的promise就失败了
            reject(reason)
          })
        })
      }
    )
  }

  /**
   * Promise函数对象的race() 任意一个promise出现成功或失败, 就直接resolve()/reject()
   *
   * @param promises promise实例对象数组
   * @return 返回一个promise, 其结果有第一个完成的promise决定
   */
  Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        // 遍历promises获取每个promise的结果
        promises.forEach(p => {
          Promise.resolve(p).then(value => {
            // 一旦有成功了, 直接return变为成功
            resolve(value)
          }, reason => {
            // 一旦有失败了, 直接return变为成功
            reject(reason)
          })
        })
      }
    )
  }

  /**
   * new Promise((resolve, reject) => resolve()) 语法糖
   *
   * @param value
   * @return 返回一个成功/失败的promise
   */
  Promise.resolve = function (value) {
    return new Promise((resolve, reject) => {
      if (value instanceof Promise) {
        // 使用value的结果作为promise的结果
        value.then(val => resolve(val), reason => reject(reason))
      } else {
        resolve(value)
      }
    })
  }

  /**
   * new Promise((resolve, reject) => reject()) 语法糖
   *
   * @param reason
   * @return 返回一个指定reason的失败的promise,不需要判断reason的数据类型
   */
  Promise.reject = function (reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }


  /**
   * 返回一个promise对象, 它在指定的时间后才确定结果
   * @param value
   * @param time 延迟时间
   * @return promise对象
   */
  Promise.resolveDelay = function (value, time) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (value instanceof Promise) {
          value.then(val => resolve(val), reason => reject(reason))
        } else {
          resolve(value)
        }
      }, time)
    })
  }

  /**
   * 返回一个promise对象, 它在指定的时间后才失败
   * @param reason
   * @param time 延迟时间
   * @return promise对象
   */
  Promise.rejectDelay = function (reason, time) {
    // 返回一个失败的promise
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(reason)
      }, time)
    })
  }


// 向外暴露Promise函数
  window.Promise = Promise
})
(window)
